""" TavrCompiler class transforms tavr program to python code. """

from S3D.Parser import Parser, SuppressErrorException

__all__ = ["TavrContext", "TavrCompiler"]

ITEM_IMPORT     = 0
# string        name        = None (for '*')
# Import        subName     = None
ITEM_IDENTIFIER = 1
# string        name
# Identifier    subName     = None
ITEM_AUTO       = 2
# string        name
# Identifier    parent      = None
# list<Var>     vars
# list<Def>     defs
ITEM_VAR        = 3
# string        name
# Expr          defVal      = None
ITEM_DEF        = 4
# string        name
# list<Var>     args
# Block         code
ITEM_BLOCK      = 5
# list<Expr>    statements
ITEM_EXPR_VAL   = 6
# object        value       # could be string, int or float
ITEM_EXPR_OPERATOR = 7
# Expr          left
# Expr          right
# int           op          # OP_*
ITEM_EXPR_UNAR  = 8
# Expr          value
# int           op          # OP_ADD, OP_SUB, OP_B_NOT, OP_L_NOT
ITEM_EXPR_RETURN = 9
# Expr          value       = None
ITEM_EXPR_RELAX = 10
# Expr          value       = None
ITEM_EXPR_CONTINUE = 11
ITEM_EXPR_BREAK = 12
ITEM_EXPR_CALL  = 13
# Expr          value
# list<Expr>    args

OP_ASSIGN       = (0, '=')
OP_ADD          = (1, '+')
OP_SUB          = (2, '-')
OP_MUL          = (3, '*')
OP_DIV          = (4, '/')
OP_MOD          = (5, '%')
OP_B_OR         = (6, '|')
OP_B_AND        = (7, '&')
OP_B_XOR        = (8, '^')
OP_B_NOT        = (9, '~')
OP_L_OR         = (10, '||')
OP_L_AND        = (11, '&&')
OP_L_NOT        = (12, '!')

ITEM_NAME       = [p for p in locals() if p.startswith('ITEM_')]
ITEM_NAME.sort(key = lambda a: globals()[a])

OP_NAME_LIST    = [v for v in locals() if v.startswith('OP_')]
OP_NAME         = [globals()[v] for v in OP_NAME_LIST]
OP_NAME.sort(key = lambda a: a[0])
OP_NAME         = [p[1] for p in OP_NAME]
for v in OP_NAME_LIST:
    locals()[v] = locals()[v][0]
del OP_NAME_LIST

class TreeItem:
    def __init__(self, type):
        self.type = type

    def __str__(self):
        if self.type == ITEM_IMPORT or self.type == ITEM_IDENTIFIER:
            if self.name is None:
                res = '*'
            else:
                res = self.name
            if not self.subName is None:
                res += '.%s' % self.subName
            return res

        if self.type == ITEM_AUTO:
            if self.parent is None:
                res = "auto %s:\n" % self.name
            else:
                res = "auto %s(%s):\n" % (self.name, self.parent)
            for v in self.vars:
                res += "   var %s\n" % v
            for d in self.defs:
                res += "   def %s\n" % d
                for cmd in d.code.statements:
                    res += "      %s\n" % cmd
            return res

        if self.type == ITEM_VAR:
            if self.defVal is None:
                return self.name
            return "%s = %s" % (self.name, self.defVal)

        if self.type == ITEM_DEF:
            return "%s%s" % (self.name, self.args)

        if self.type == ITEM_EXPR_RETURN or self.type == ITEM_EXPR_RELAX:
            if not self.value is None:
                if self.type == ITEM_EXPR_RETURN:
                    return "return %s" % self.value
                else:
                    return "relax %s" % self.value

        table = {
            ITEM_EXPR_RELAX:    "relax",
            ITEM_EXPR_RETURN:   "return",
            ITEM_EXPR_CONTINUE: "continue",
            ITEM_EXPR_BREAK:    "break",
        }
        if self.type in table.keys():
            return table[self.type]

        if self.type == ITEM_EXPR_OPERATOR:
            return "(%s %s %s)" % (self.left, OP_NAME[self.op], self.right)

        if self.type == ITEM_EXPR_UNAR:
            return "%s%s" % (OP_NAME[self.op], self.value)

        if self.type == ITEM_EXPR_VAL:
            return self.value

        if self.type == ITEM_EXPR_CALL:
            return "%s%s" % (self.value, self.args)

        raise Exception("Unknown TreeItem type: %s" % ITEM_NAME[self.type])

    def __repr__(self):
        return str(self)

class TavrContext:
    def __init__(self, c):
        varList = [
            "imports",
            "events",
            "autos",
            "defs",
        ]

        for v in varList:
            setattr(self, v, getattr(c, v))

class TavrCompiler(Parser):
    def parse(self):
        self.imports = []
        self.events = []
        self.autos = []
        self.defs = []

        self.parseImports()

        while not self.nextIsToken('eof'):
            if self.nextIsWord('auto'):
                self.parseAuto()
            elif self.nextIsWord('def'):
                self.defs.append(self.parseDefDecl())
            else:
                self.matchNothing()

        return TavrContext(self)

    def parseImports(self):
        while self.nextIsWord('import', match = True):
            item            = TreeItem(ITEM_IMPORT)
            item.name       = self.matchWord()
            item.subName    = None

            cur = item
            while self.nextIsSymbol('.', match = True):
                subName = TreeItem(ITEM_IMPORT)
                subName.subName = None
                
                if self.nextIsSymbol('*', match = True):
                    subName.name = None
                    cur.subName = subName
                    break
                    
                subName.name = self.matchWord()
                cur.subName = subName
                cur = subName

            self.matchSymbol(';')
            self.imports.append(item)

    def parseAuto(self):
        self.matchWord('auto')

        decl = TreeItem(ITEM_AUTO)
        decl.name   = self.matchWord()
        decl.parent = None

        if self.nextIsSymbol(':', match = True):
            decl.parent = self.parseIdentifier()

        self.matchSymbol('{')

        decl.vars = []
        decl.defs = []

        while not self.nextIsSymbol('}', match = True):
            if self.nextIsWord('var'):
                decl.vars.extend(self.parseVarDecl())
            elif self.nextIsWord('def'):
                decl.defs.append(self.parseDefDecl())
            else:
                self.matchSymbol('}')

        self.autos.append(decl)

    def parseVarDecl(self):
        self.matchWord('var')

        res = [self.parseVar()]

        while not self.nextIsSymbol(';', match = True):
            self.matchSymbol(',')
            res.append(self.parseVar())

        return res

    def parseDefDecl(self):
        self.matchWord('def')

        item = TreeItem(ITEM_DEF)
        item.name = self.matchWord()

        self.matchSymbol('(')

        item.args = []
        if not self.nextIsSymbol(')', match = True):
            item.args.append(self.parseVar())

            while not self.nextIsSymbol(')', match = True):
                self.matchSymbol(',')
                item.args.append(self.parseVar())

        item.code = self.parseBlock()

        return item

    def parseBlock(self):
        self.matchSymbol('{')
        
        block = TreeItem(ITEM_BLOCK)
        block.statements = []

        while not self.nextIsSymbol('}', match = True):
            if self.nextIsSymbol(';', match = True):
                continue

            block.statements.append(self.parseStatement())

        return block

    def parseStatement(self):
        if self.nextIsSymbol('{'):
            return self.parseBlock()

        if self.nextIsWord('return', match = True):
            res = TreeItem(ITEM_EXPR_RETURN)

            if not self.nextIsSymbol(';'):
                res.value = self.parseExpr()

            self.matchSymbol(';')
            return res

        if self.nextIsWord(['continue', 'break']):
            if self.getNextValue() == 'continue':
                res = TreeItem(ITEM_EXPR_CONTINUE)
            else:
                res = TreeItem(ITEM_EXPR_BREAK)
            self.matchAny()
            self.matchSymbol(';')
            return res

        expr = self.parseExpr()
        self.matchSymbol(';')
        return expr

    def parseVar(self):
        var = TreeItem(ITEM_VAR)
        var.name    = self.matchWord()
        var.defVal  = None
        if self.nextIsSymbol('=', match = True):
            var.defVal = self.parseExpr()
        return var

    def parseExpr(self):
        self.pushSuppressError()
        
        try:
            self.parseIdentifier()
            self.matchSymbol('=')
            self.matchNot(value = '=')
            self.popSuppressError()

            return self.parseExprAssign()
        except SuppressErrorException:
            pass

        self.popSuppressError()
        return self.parseExprOr()

    def parseExprAssign(self):
        expr = TreeItem(ITEM_EXPR_OPERATOR)
        expr.left   = self.parseIdentifier()
        self.matchSymbol('=')
        expr.right  = self.parseExpr()
        expr.op     = OP_ASSIGN
        return expr

    def parseExprOpTpl(self, nextMethod, selfMethod, op):
        res             = nextMethod()

        if isinstance(op, list):
            for p in op:
                if self.nextIsOperator(OP_NAME[p], match = True):
                    left        = res
                    res         = TreeItem(ITEM_EXPR_OPERATOR)
                    res.left    = left
                    res.right   = selfMethod()
                    res.op      = p
        elif self.nextIsOperator(OP_NAME[op], match = True):
            left        = res
            res         = TreeItem(ITEM_EXPR_OPERATOR)
            res.left    = left
            res.right   = selfMethod()
            res.op      = op

        return res

    def parseExprOr(self):      return self.parseExprOpTpl(self.parseExprAnd,       self.parseExprOr,       OP_L_OR)
    def parseExprAnd(self):     return self.parseExprOpTpl(self.parseExprAddSub,    self.parseExprAnd,      OP_L_AND)
    def parseExprAddSub(self):  return self.parseExprOpTpl(self.parseExprMulDiv,    self.parseExprAddSub,   [OP_ADD, OP_SUB])
    def parseExprMulDiv(self):  return self.parseExprOpTpl(self.parseExprBinOr,     self.parseExprMulDiv,   [OP_MUL, OP_DIV, OP_MOD])
    def parseExprBinOr(self):   return self.parseExprOpTpl(self.parseExprBinAnd,    self.parseExprBinOr,    OP_B_OR)
    def parseExprBinAnd(self):  return self.parseExprOpTpl(self.parseExprUnar,      self.parseExprBinAnd,   OP_B_AND)

    def parseExprUnar(self):
        if self.nextIsSymbol(['-', '+', '!', '~']):
            cur = TreeItem(ITEM_EXPR_UNAR)
            table = {
                '-': OP_SUB,
                '+': OP_ADD,
                '!': OP_L_NOT,
                '~': OP_B_NOT,
            }
            cur.op = table[self.getNextValue()]
            self.matchAny()

            cur.value = self.parseExprUnar()
            return cur

        return self.parseExprValue()

    def parseExprValue(self):
        if self.nextIsSymbol('(', match = True):
            res = self.parseExprOr()
            self.matchSymbol(')')
            return res

        if self.nextIsWord():
            val = self.parseIdentifier()
            return self.parseExprCall(val)

        return self.matchValue()

    def parseExprCall(self, left):
        if not self.nextIsSymbol('(', match = True):
            return left

        call = TreeItem(ITEM_EXPR_CALL)
        call.value = left
        call.args = []

        if not self.nextIsSymbol(')', match = True):
            call.args.append(self.parseExprOr())

            while not self.nextIsSymbol(')', match = True):
                call.args.append(self.parseExprOr())

        return self.parseExprCall(call)

    def parseIdentifier(self):
        res = TreeItem(ITEM_IDENTIFIER)
        res.name    = self.matchWord()
        res.subName = None

        cur = res
        while self.nextIsSymbol('.', match = True):
            subName = TreeItem(ITEM_IDENTIFIER)
            subName.name    = self.matchWord()
            subName.subName = None
            cur.subName     = subName
            cur             = subName

        return res

if __name__ == "__main__":
    c = TavrCompiler()
    r = c.compile("""
        import sys;
        import S3D.TavrCompiler.*;

        auto MyAuto {
            var name = "Hello world";
            var otherVar;
        }

        auto SomeOtherAuto : Globalle.MyAuto {
            def main(a = 15, b, c = "kuku") {
                a = b = (10 + 1) * a;
                return (1 + 2) * a % d;
                print("Hello world");
            }
        }
    """, file = "TavrCompiler.py", lineOffset = 314)

    if r is None:
        import sys
        print(c.errorMsg)
        print("%d errors." % c.errorsCount)
        sys.exit(-1)

    for v in r.imports:
        print('import', v)
    print()

    for a in r.autos:
        print(a)

    print("DONE.")
